﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Security;
using System.Text;
using System.Xml.Linq;
using Slf;
using yaTDL.Extensions;
using System.Xml;
using System.Threading;
using yaTDL.Core.Toodledo;

namespace yaTDL.Core
{
    public class ApplicationSettings: NotifyObject
    {

        ILogger logger = LoggerService.GetLogger("ApplicationSettings");

        Dictionary<string, object> values = new Dictionary<string, object>();

        Dictionary<string, object> defaultValues = new Dictionary<string, object>();



        static ApplicationSettings()
        {
            verifySettings();

            try
            {
                Settings.Reload();
            }
            catch (Exception)
            {
                File.WriteAllText(filePath, Properties.Resources.SettingsDefault);
                Settings.Reload();
            }
        }


      
        #region Properties

        public int Backup_Count
        {
            get { return (int)values["Backup_Count"]; }
            set
            {
                if (value != (int)values["Backup_Count"])
                {
                    values["Backup_Count"] = value; 
                    RaisePropertyChanged("Backup_Count");                    
                }
            }
        }

        public string Backup_BackupDirectory
        {
            get { return (string)values["Backup_BackupDirectory"]; }
            set 
            {
                if (value != (string)values["Backup_BackupDirectory"])
                {
                    values["Backup_BackupDirectory"] = value;
                    RaisePropertyChanged("Backup_BackupDirectory");                    
                }
            }
        }

        public bool Backup_Enable
        {
            get { return (bool)values["Backup_Enable"]; }
            set 
            {
                if (value != (bool)values["Backup_Enable"])
                {
                    values["Backup_Enable"] = value;
                    RaisePropertyChanged("Backup_Enable");                   
                }
            }
        }

        public TimeSpan Backup_Interval
        {
            get { return (TimeSpan)values["Backup_Interval"]; }
            set 
            {
                if (value != (TimeSpan)values["Backup_Interval"])
                {
                    values["Backup_Interval"] = value;
                    RaisePropertyChanged("Backup_Interval");                    
                }
            }
        }

        public string Backup_FilePrefix
        {
            get { return (string)values["Backup_FilePrefix"]; }
            set 
            {
                if (value != (string)values["Backup_FilePrefix"])
                {
                    values["Backup_FilePrefix"] = value;
                    RaisePropertyChanged("Backup_FilePrefix");                    
                }
            }
        }

        public bool Backup_KeepAll
        {
            get { return (bool)values["Backup_KeepAll"]; }
            set 
            {
                if (value != (bool)values["Backup_KeepAll"])
                {
                    values["Backup_KeepAll"] = value;
                    RaisePropertyChanged("Backup_KeepAll");
                }
            }
        }

        public DateTime Backup_LastBackup
        {
            get { return (DateTime)values["Backup_LastBackup"]; }
            set
            {
                if (value != (DateTime)values["Backup_LastBackup"])
                {
                    values["Backup_LastBackup"] = value;
                    RaisePropertyChanged("Backup_LastBackup");
                }
            }
        }

        public DateTime Toodledo_LastSync
        {
            get { return (DateTime)values["Toodledo_LastSync"]; }
            set 
            {
                if (value != (DateTime)values["Toodledo_LastSync"])
                {                  
                    values["Toodledo_LastSync"] = value;
                    RaisePropertyChanged("Toodledo_LastSync");
                }
            }
        }

        public TimeSpan Toodledo_SyncInterval
        {
            get { return (TimeSpan)values["Toodledo_SyncInterval"]; }
            set 
            {
                if (value != (TimeSpan)values["Toodledo_SyncInterval"])
                {
                    values["Toodledo_SyncInterval"] = value;
                    RaisePropertyChanged("Toodledo_SyncInterval");
                }
            }
        }

        public bool Toodledo_EnableSync
        {
            get { return (bool)values["Toodledo_EnableSync"]; }
            set 
            {
                if (value != (bool)values["Toodledo_EnableSync"])
                {
                    values["Toodledo_EnableSync"] = value;
                    RaisePropertyChanged("Toodledo_EnableSync");
                }
            }
        }

        public SecureString Toodledo_UserId
        {
            get { return (SecureString)values["Toodledo_UserId"]; }
            set
            {
                if (value != (SecureString)values["Toodledo_UserId"])
                {
                    values["Toodledo_UserId"] = value;
                    RaisePropertyChanged("Toodledo_UserId");
                }
            }
        }

        public SecureString Toodledo_Password
        {
            get { return (SecureString)values["Toodledo_Password"]; }
            set 
            {
                if (value != (SecureString)values["Toodledo_Password"])
                {
                    values["Toodledo_Password"] = value;
                    RaisePropertyChanged("Toodledo_Password");
                }
            }
        }

        public ToodledoAPIKey Toodledo_APIKey
        {
            get { return (ToodledoAPIKey)values["Toodledo_APIKey"]; }
            set
            {
                if (value != (ToodledoAPIKey)values["Toodledo_APIKey"])
                {
                    values["Toodledo_APIKey"] = value;
                    RaisePropertyChanged("Toodledo_APIKey");
                }
            }
        }

        public bool Logging_Enable
        {
            get { return (bool)values["Logging_Enable"]; }
            set 
            {
                if (value != (bool)values["Logging_Enable"])
                {
                    values["Logging_Enable"] = value;
                    RaisePropertyChanged("Logging_Enable");
                }
            }
        }

        public TimeSpan TaskStore_WriteInterval
        {
            get { return (TimeSpan)values["TaskStore_WriteInterval"]; }
            set
            {
                if (value != (TimeSpan)values["TaskStore_WriteInterval"])
                {
                    values["TaskStore_WriteInterval"] = value;
                    RaisePropertyChanged("TaskStore_WriteInterval");
                }
            }
        }

        #endregion


        static void verifySettings()
        {

            if (!File.Exists(filePath))
            {
                File.WriteAllText(filePath, Properties.Resources.SettingsDefault);
            }
            else
            {
                XDocument defaultDoc = XDocument.Parse(Properties.Resources.SettingsDefault);

                XDocument currentDoc = XDocument.Parse(File.ReadAllText(filePath));

                if (currentDoc.Root.Name != "settings")
                {
                    File.WriteAllText(filePath, Properties.Resources.SettingsDefault);
                }
                
                var missingProperties = from property in defaultDoc.Descendants("property")
                                        where !currentDoc.Descendants("property").Where(x => x.Attribute("name").Value == property.Attribute("name").Value).Any()
                                        select property;

                foreach (XElement item in missingProperties)
                {
                    currentDoc.Element("settings").Add(item);
                }

                File.WriteAllText(filePath, currentDoc.ToString());
            }
        }


        #region Public Methods


        /// <summary>
        /// Reloads the values of all properties from file.
        /// </summary>
        public void Reload()
        {
            logger.Info("Loading Settings from {0}", filePath);

            XDocument settings = XDocument.Parse(File.ReadAllText(filePath));

            defaultValues.Clear();
            values.Clear();

            foreach (XElement xElement in settings.Descendants("property"))
            {
                Type type = Type.GetType(xElement.Attribute("type").Value);

                defaultValues.Add(xElement.Attribute("name").Value, parseData(type, xElement.Attribute("defaultValue").Value));
                
                if (String.IsNullOrEmpty(xElement.Value))
                {
                    values.Add(xElement.Attribute("name").Value, defaultValues[xElement.Attribute("name").Value] ); 
                }
                else
                {
                    values.Add(xElement.Attribute("name").Value, parseData(type, xElement.Value));
                }

                RaisePropertyChanged(xElement.Attribute("name").Value);
            }
 
        }

        /// <summary>
        /// Reloads the values of the specified properties from file.
        /// </summary>
        /// <param name="propertyNames">The names of the properties to be reloaded.</param>
        public void Reload(params string[] propertyNames)
        {
            logger.Info("Reloading following properties: {0}", propertyNames.ToCombinedString());

            XDocument settings = XDocument.Parse(File.ReadAllText(filePath));

            foreach (string name in propertyNames)
            {
                if (values.ContainsKey(name))
                {
                    XElement element = settings.Descendants("property").First(x => x.Attribute("name").Value == name);

                    Type type = Type.GetType(element.Attribute("type").Value);

                    defaultValues[name] = parseData(type, element.Attribute("defaultValue").Value);

                    object value;

                    if (String.IsNullOrEmpty(element.Value))
                    {
                        value = defaultValues[name];
                    }
                    else
                    {
                        value = parseData(type, element.Value);
                    }

                    if (value != values[name])
                    {
                        values[name] = value;
                        RaisePropertyChanged(name);
                    }
                }               
            }

        }

        /// <summary>
        /// Reloads values of all properties except the indicated ones from file.
        /// </summary>
        /// <param name="excludedPropertyNames">The names of the properties not to be reloaded.</param>
        public void ReloadAllBut(params string[] excludedPropertyNames)
        {
            logger.Info("Reloading all properties but {0}", excludedPropertyNames.ToCombinedString());

            XDocument settings = XDocument.Parse(File.ReadAllText(filePath));

            List<string> propertiesToBeSaved = values.Keys.Where(x=> !excludedPropertyNames.Contains(x)).ToList<string>();

            foreach (string name in propertiesToBeSaved)
            {
                XElement element = settings.Descendants("property").First(x => x.Attribute("name").Value == name);

                Type type = Type.GetType(element.Attribute("type").Value);

                defaultValues[name] = parseData(type, element.Attribute("defaultValue").Value);

                if (String.IsNullOrEmpty(element.Value))
                {
                    values[name] = defaultValues[name];
                }
                else
                {
                    values[name] = parseData(type, element.Value);
                }
            }

        }

        public void Save()
        {
            logger.Info("Writing Settings to {0}", filePath);

            XDocument xdoc = new XDocument(new XComment("Settings file used by yaTDL. Edit only if you're sure what you are doing"));

            XElement settings = new XElement("settings");

            xdoc.Add(settings);

            foreach (string name in values.Keys)
            {
                object value = values[name];

                XElement property = new XElement("property");
                property.Add(new XAttribute("name", name));
                property.Add(new XAttribute("type", values[name].GetType().FullName));
                property.Add(new XAttribute("defaultValue", serializeData(defaultValues[name])));
                property.Value = serializeData(values[name]);
                settings.Add(property);
            }

            try
            {
                string xdoc_string = xdoc.ToString();
                File.WriteAllText(filePath, xdoc.ToString());
            }
            catch (Exception)
            {
                Save();
                return;
            }

        }

        public void Save(params string[] propertyNames)
        {
            logger.Info("Writing {1} to {0}", propertyNames.ToCombinedString(), filePath);


            XDocument settings = XDocument.Parse(File.ReadAllText(filePath));

            foreach (string pName in propertyNames)
            {
                settings.Descendants("property").Where(x => x.Attribute("name").Value == pName).First().Value = serializeData(values[pName]);                
            }

            File.WriteAllText(filePath, settings.Root.ToString());
        }

        public void SaveAllBut(params string[] excludedPropertyNames)
        {
            if (!excludedPropertyNames.Any())
            {
                Save();
                return;
            }
            
            logger.Info("Saving all properties but {1} to {0}", filePath, excludedPropertyNames.ToCombinedString());

            XDocument settings = XDocument.Parse(File.ReadAllText(filePath));

            foreach (string pName in values.Values.Where(x=> !excludedPropertyNames.Contains(x)))
            {
                settings.Descendants("property").Where(x => x.Attribute("Name").Value == pName).First().Value = serializeData(values[pName]);
            }

        }

        public void Reset()
        {
            logger.Info("Resetting all Properties");

            foreach (string name in values.Keys)
            {
                Reset(name);
            }
        }

        public void Reset(params string[] propertyNames)
        {
            logger.Info("Resetting {0}", propertyNames.ToCombinedString());

            foreach (string str in propertyNames)
            {
                bool changed = false;

                if (values[str] != defaultValues[str])
                {
                    changed = true;
                }

                values[str] = defaultValues[str];

                if (changed)
                {
                    RaisePropertyChanged(str);
                }

            }
        }

        public void ResetAllBut(params string[] excludedPropertyNames)
        {
            foreach (string key in values.Values.Where(x=> !excludedPropertyNames.Contains(x)))
            {
                Reset(key);
            }
        }

        #endregion

        #region Static Members

#if DEBUG
        private static string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"yaTDL_Debug", "Settings.xml");
#else
        private static string filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"yaTDL", "Settings.xml");
#endif        


        static ApplicationSettings _settings = new ApplicationSettings();

        public static ApplicationSettings Settings
        {
            get { return _settings; }
        }


        private static object parseData(Type type, string data)
        {
            object value = null;

            if (type == typeof(string))
            {
                value = data.Replace("%APPDATA%", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
            }
            else if (type == typeof(int))
            {
                value = int.Parse(data);
            }
            else if (type == typeof(Boolean))
            {
                value = bool.Parse(data);
            }
            else if (type == typeof(DateTime))
            {
                value = DateTime.Parse(data);
            }
            else if (type == typeof(TimeSpan))
            {
                value = TimeSpan.Parse(data);
            }
            else if (type == typeof(SecureString))
            {
                value = data.DecryptSecure();
            }
            else if (type == typeof(ToodledoAPIKey))
            {
                ToodledoAPIKey key = new ToodledoAPIKey();
                if (data.Contains(";"))
                {
                    string[] strs = data.Split(new char[] { ';' });
                    key.Key = strs.First();

                    if (!String.IsNullOrEmpty(strs.Last()))
                    {
                        key.Expires = DateTime.Parse(strs.Last());
                    }                    
                }

                value = key;
            }

            return value;
        }

        private static string serializeData(object data)
        {
            Type type = data.GetType();

            if (type == typeof(string))
            {
                return (string)data;
            }
            else if (type == typeof(int))
            {
                return ((int)data).ToString();
            }
            else if (type == typeof(Boolean))
            {
                return ((bool)data).ToString();
            }
            else if (type == typeof(DateTime))
            {
                return ((DateTime)data).ToString();
            }
            else if (type == typeof(TimeSpan))
            {
                return ((TimeSpan)data).ToString();
            }
            else if (type == typeof(SecureString))
            {
                return ((SecureString)data).Unwrap().Encrypt();
            }
            else if (type == typeof(ToodledoAPIKey))
            {
                return String.Format("{0};{1}", ((ToodledoAPIKey)data).Key, ((ToodledoAPIKey)data).Expires.ToString());
            }
            else
            {
                return null;
            }
        }

        #endregion

    }
}
